﻿using System;
using System.Collections.Generic;
using System.Linq;

using OctoOS;
using OctoOS.Services;
 
/// <summary>
/// Summary description for ApiService
/// </summary>
/// 
 
public static class ApiService
{
	const int _maxMessageCount = 100;
    const int _maxCategoryCount = 1000;
	const int _publishArticlesCount = 10;
	const int _featuredArticlesCount = 3;

    /// <summary>
    /// Удаляем 3 рутовых ентити
    /// </summary>
    /// <param name="request"></param>
	public static void DeleteAll(OOSRequest request)
	{
        var response = OctoServices.Process(request,
            new Entity_Delete(EntityTypes.Root, EntityIDs.MainCategoryID),
            new Entity_Delete(EntityTypes.Root, EntityIDs.PublishCategoryID),
            new Entity_Delete(EntityTypes.Root, EntityIDs.CategoriesRootID));

		CheckResponse(response);
	}

	private static void CheckResponse(MessageOut[] messages)
	{
		foreach(var message in messages)
			CheckResponse(message);
	}

	private static void CheckResponse(MessageOut message)
	{
		if(message as ErrorMessageResponse != null)
			throw new ApplicationException();
	}

	public static void DeleteArticle(OOSRequest request, long id)
	{
		var response = OctoServices.Process(request,
			new Entity_Delete(EntityTypes.Article, id));

		CheckResponse(response);
	}

    /// <summary>
    /// Автоматическое открытие и закрытие артиклов
    /// </summary>
    /// <param name="request"></param>
    /// <param name="checkTime"></param>
	public static void AutoPublishArticles(OOSRequest request, DateTime checkTime)
	{
		List<MessageIn> publishMessages = new List<MessageIn>();

        //запрашиваем артиклы для открытия и закрытия
        var responses = OctoServices.Process(request,
            new Entity_GetChildren_Index(EntityTypes.Root, EntityIDs.PublishCategoryID,
                PublishIndexes.OpenPublish, 0, _publishArticlesCount, false) { ReturnParentIDsRelationType = 0 },
			new Entity_GetChildren_Index(EntityTypes.Root, EntityIDs.PublishCategoryID,
				PublishIndexes.ClosePublish, 0, _publishArticlesCount, false) { ReturnParentIDsRelationType = 0 });

		for(int i = 0; i < responses.Length; i++)
		{
			var response = responses[i].CastOrHttpException<Entity_GetChildPreviews_Response>();

			if(response.Previews != null)
			{
                var allCategories = AppUtil.GetCategories(OOSRequest.Current);

				foreach(var preview in response.Previews)
				{
					var previewMeta = ArticleMeta.Create(preview.Properties[0].Value);

					//если дата старта/стопа статьи ещё не подошла тогда не обрабатываем её 
                    if(i == 0)
					//if((requestMessagesList[i] as Post_GetGroupThreadPreviews).Index == (int)PublishCategoryIndexes.StartPublish)
					{
						if(previewMeta.PublishStartTime > DateTime.UtcNow)
						{
							continue;
						}
						else
						{
							//публикуем статью, обновляем группы - добавляем в главную группу, группу юзера и все категории
							List<EntityRelation> relations = new List<EntityRelation>();
							relations.Add(new EntityRelation(EntityTypes.Root, EntityIDs.MainCategoryID, true, false) 
								{ IndexValue1 = IndexBuilder.GetBytes(previewMeta.PublishStartTime) });

#warning сделать личный кабинет
							//добавляем ентити пользователя если его ещё нет
							//publishMessages.Add(new Entity_Add(EntityTypes.User, previewMeta.UserID, "user") { OnlyIfNotExists = true });
							relations.Add(new EntityRelation(EntityTypes.User, previewMeta.UserID, true, false)
								{
									IndexValue2 = IndexBuilder.GetBytes(previewMeta.PublishStartTime)
								});

                            foreach(var categoryID in preview.ParentIDs)
                            {
                                if(categoryID != EntityIDs.MainCategoryID && categoryID != EntityIDs.PublishCategoryID)
                                {
                                    //получаем настоящую категорию статьи
                                    var currentCategory = AppUtil.GetCategory(allCategories, categoryID);
                                    if(currentCategory == null)
                                        throw new ApplicationException();

                                    bool featured;
                                    bool published;
                                    currentCategory.GetArticleStatus(categoryID, out featured, out published);

                                    //there must be only not published articles
                                    if(published)
                                        throw new ApplicationException();

                                    var publishedCategoryID = currentCategory.GetArticleCategoryID(featured, true);

                                    relations.Add(new EntityRelation(EntityTypes.Root, publishedCategoryID, true, false)
                                        {
                                            IndexValue1 = IndexBuilder.GetBytes(DateTime.UtcNow)
                                        });
                                }
                            }

							//добавляем в группу остановки публикации если нужно
							if(previewMeta.PublishStopTime > DateTime.UtcNow && previewMeta.PublishStopTime != DateTime.MaxValue)
							{
								relations.Add(new EntityRelation(EntityTypes.Root, EntityIDs.PublishCategoryID, false, false)
								{
									IndexValue2 = IndexBuilder.GetBytes(previewMeta.PublishStopTime)
								});
							}

							publishMessages.Add(
									new Entity_UpdateRelations(EntityTypes.Article, preview.EntityID, true, relations.ToArray()));
						}
					}
					else
					{
						if(previewMeta.PublishStopTime > DateTime.UtcNow)
						{
							continue;
						}
						else
						{
							//закрываем статью - оставляем только группу юзера и категорий (not published)
							List<EntityRelation> relations = new List<EntityRelation>();
							relations.Add(new EntityRelation(EntityTypes.User, previewMeta.UserID, true, false)
							{
								IndexValue3 = IndexBuilder.GetBytes(previewMeta.PublishStopTime)
							});

                           
                            foreach(var categoryID in preview.ParentIDs)
                            {
                                if(categoryID != EntityIDs.MainCategoryID && categoryID != EntityIDs.PublishCategoryID)
                                {
                                    var currentCategory = AppUtil.GetCategory(allCategories, categoryID);
                                    if(currentCategory == null)
                                        throw new ApplicationException();

                                    bool featured; bool published;
                                    currentCategory.GetArticleStatus(categoryID, out featured, out published);

                                    var newCategoryID = currentCategory.GetArticleCategoryID(featured, false);

                                    relations.Add(new EntityRelation(EntityTypes.Category, newCategoryID, true, false));
                                }
                            }

							publishMessages.Add(
									new Entity_UpdateRelations(EntityTypes.Article, preview.EntityID, true, relations.ToArray()));
						}
					}
				}
			}
		}

		if(publishMessages.Count > 0)
			SendMessagesSafe(request, publishMessages.ToArray());
	}

	public static void InitResource(OOSRequest request)
	{
        var response = OctoServices.Process(request,
            new Entity_Add(EntityTypes.Root, EntityIDs.MainCategoryID, "Main Category", 
				new Property(new Settings().ToArray(), 0)) { OnlyIfNotExists = true },
            new Entity_Add(EntityTypes.Root, EntityIDs.PublishCategoryID, "Publish Category") 
            { OnlyIfNotExists = true },
            new Entity_Add(EntityTypes.Root, EntityIDs.CategoriesRootID, "Categories Root")
            { OnlyIfNotExists = true });

        CheckResponse(response);
	}

	public static void InsertCategory(OOSRequest request, string categoryName)
	{
		var response = OctoServices.Process(request,
				new Entity_Add(EntityTypes.Category, request.RequestID, categoryName,
					new EntityRelation(EntityTypes.Root, EntityIDs.CategoriesRootID, true, false) { IndexValue1 = IndexBuilder.GetBytes(categoryName) }),

				new Entity_Add(EntityTypes.Category, request.RequestID + (int)ArticleCategoryStatus.NotPublishedNotFeatured,
					"Not published not featured",
					new EntityRelation(EntityTypes.Category, request.RequestID, true, false) 
					{ RelationType = CategoryRelationTypes.ChildCategory }),

				new Entity_Add(EntityTypes.Category, request.RequestID + (int)ArticleCategoryStatus.NotPublishedFeatured,
					"Not Published/Featured", new EntityRelation(EntityTypes.Category, request.RequestID, true, false) 
					{ RelationType = CategoryRelationTypes.ChildCategory }),

				new Entity_Add(EntityTypes.Category, request.RequestID + (int)ArticleCategoryStatus.PublishedNotFeatured,
					"Published/Not Featured", new EntityRelation(EntityTypes.Category, request.RequestID, true, false) 
					{ RelationType = CategoryRelationTypes.ChildCategory }),

				new Entity_Add(EntityTypes.Category, request.RequestID + (int)ArticleCategoryStatus.PublishedFeatured,
					"Published/Featured", new EntityRelation(EntityTypes.Category, request.RequestID, true, false) 
					{ RelationType = CategoryRelationTypes.ChildCategory }));

		CheckResponse(response);
	}

	public static void UpdateCategory(OOSRequest request, long categoryID, string categoryName)
	{
        var response = OctoServices.Process(request,
            new Entity_Update(EntityTypes.Category, categoryID) { Title = categoryName });

        CheckResponse(response);
	}

	public static void DeleteCategory(OOSRequest request, long categoryID)
	{
        var response = OctoServices.Process(request, new Entity_Delete(EntityTypes.Category, categoryID));

        CheckResponse(response);
	}


	public static Category[] GetCategories(OOSRequest request)
	{
        List<Category> categories = new List<Category>();

        var response = OctoServices.Process(request,
            new Entity_GetChildPreviews_Index(EntityTypes.Root, EntityIDs.CategoriesRootID,
				(byte)CategoryRootIndexes.CategoryName, 0, _maxCategoryCount, false) 
            ).CastOrHttpException<Entity_GetChildPreviews_Response>();

        if(response.Previews != null)
        {
            foreach(var preview in response.Previews)
                categories.Add(new Category(preview.EntityID, preview.Title));
        }

		return categories.ToArray();
	}

	public static Category GetCategory(OOSRequest request, long categoryID)
	{
        var response = OctoServices.Process(request,
            new Entity_Get(EntityTypes.Category, categoryID)).CastOrHttpException<Entity_Get_Response>();

        return new Category(response.Entity.ID, response.Entity.Title);
	}

	public static Settings GetSettings(OOSRequest request)
	{
        var mainCategory = OctoServices.Process(request,
            new Entity_Get(EntityTypes.Root, EntityIDs.MainCategoryID, 0) 
            ).CastOrHttpException<Entity_Get_Response>();

		return Settings.Create(mainCategory.Entity.Properties[0].Value);
	}

    /// <summary>
    /// Save settings in Main category entity
    /// </summary>
	public static void SaveSettings(OOSRequest request, CommentsEnabled commentsEnabled,
		CommentsModeration commentsModeration, FeedMode feedModeSelect,
		int disableAfter, bool enableSubtitle, bool syndicatePosts, bool bookmarks,
		bool sendToFriend, bool print, //bool trackBarUrl, 
		bool commentRss)
	{
		Settings meta = new Settings(commentsEnabled,
			commentsModeration, feedModeSelect, disableAfter, enableSubtitle,
			syndicatePosts, bookmarks, sendToFriend, print, //trackBarUrl, 
			commentRss);

		var messageOut = OctoServices.Process(request,
			new Entity_Update(EntityTypes.Root, EntityIDs.MainCategoryID, new Property(meta.ToArray(), 0)));

		CheckResponse(messageOut);
	}

#warning сделать добавление в группу юзера
    /// <param name="categories">categoryID, featured</param>
	public static void InsertArticle(OOSRequest request, string author,
		string title, string preface, DateTime publishStartTime, DateTime publishStopTime, 
        Dictionary<long, bool> categories) 
	{
		ArticleMeta meta = new ArticleMeta(preface, publishStartTime, publishStopTime,
			request.UserID, author, DateTime.UtcNow);

        List<EntityRelation> relations = new List<EntityRelation>();
		var userRelation = new EntityRelation(EntityTypes.User, request.UserID, true, false);
		relations.Add(userRelation);

		foreach(var categoryID in categories.Keys)
		{
			EntityRelation relation = null;
			bool featured = categories[categoryID];

			if(meta.PublishedNow)
			{
				if(featured)
				{
					relation = new EntityRelation(EntityTypes.Category, 
						categoryID + (byte)ArticleCategoryStatus.PublishedFeatured, true, false) 
						{ IndexValue1 = IndexBuilder.GetBytes(meta.PublishStartTime) };
				}
				else
				{
					relation = new EntityRelation(EntityTypes.Category,
						categoryID + (byte)ArticleCategoryStatus.PublishedNotFeatured, true, false) 
						{ IndexValue1 = IndexBuilder.GetBytes(meta.PublishStartTime) };
				}

				//add to main category index
				relations.Add(new EntityRelation(EntityTypes.Category, categoryID, true, false)
				{
					RelationType = CategoryRelationTypes.Article,
					IndexValue1 = IndexBuilder.GetBytes(meta.PublishStartTime)
				});
			}
			else
			{
				if(featured)
				{
					relation = new EntityRelation(EntityTypes.Category,
						categoryID + (byte)ArticleCategoryStatus.NotPublishedFeatured, true, false) 
						{ IndexValue1 = IndexBuilder.GetBytes(meta.CreateTime) };
				}
				else
				{
					relation = new EntityRelation(EntityTypes.Category,
						categoryID + (byte)ArticleCategoryStatus.NotPublishedNotFeatured, true, false) 
						{ IndexValue1 = IndexBuilder.GetBytes(meta.CreateTime) };
				}
			}

			relations.Add(relation);
		}

		//если статья опубликована, то добавляем её в главную группу
		if(meta.PublishedNow)
		{
			relations.Add(new EntityRelation(EntityTypes.Root, EntityIDs.MainCategoryID, true, false) 
				{ 
					IndexValue1 = IndexBuilder.GetBytes(meta.PublishStartTime),
					IndexValue2 = IndexBuilder.GetBytes(meta.PublishStartTime)
				});

			userRelation.IndexValue2 = IndexBuilder.GetBytes(meta.PublishStartTime);
		}

        //добавляем в ентити очереди публикаций
		
		var publishRelation = new EntityRelation(EntityTypes.Root, EntityIDs.PublishCategoryID, false, false);
		if(publishStartTime > DateTime.UtcNow && publishStartTime != DateTime.MaxValue)
		{
			publishRelation.IndexValue1 = IndexBuilder.GetBytes(publishStartTime);
			userRelation.IndexValue1 = IndexBuilder.GetBytes(publishStartTime);
		}

		if(publishStopTime > DateTime.UtcNow && publishStopTime != DateTime.MaxValue)
		{
			publishRelation.IndexValue2 = IndexBuilder.GetBytes(publishStopTime);
			userRelation.IndexValue3 = IndexBuilder.GetBytes(publishStopTime);
		}
		

		if(publishRelation.IndexValue1 != null || publishRelation.IndexValue2 != null)
			relations.Add(publishRelation);

		List<MessageIn> mesIn = new List<MessageIn>();

		mesIn.Add(new Entity_Add(EntityTypes.User, request.UserID, "user") { OnlyIfNotExists = true });
		mesIn.Add(new Entity_Add(EntityTypes.Article, request.RequestID, title, new Property(meta.ToArray(), 0),
				relations.ToArray()) {  Locked = !meta.PublishedNow  });
		//mesIn.Add(new Contlet_Add(EntityTypes.Article, request.RequestID, 1, 0, "", new ContletBody("", TextFormat.Html),
		//	ContletFlags.Obsolete_AppRendering));

		var messageOut = OctoServices.Process(request, mesIn.ToArray());
		CheckResponse(messageOut);
	}


	public static void UpdateArticle(OOSRequest request, long articleID, string author, string title, string preface, 
        DateTime publishStartTime, DateTime publishStopTime, Dictionary<long, bool> categories)
	{
		var currentArticle = OctoServices.Process(request,
			new Entity_Get(EntityTypes.Article, articleID, 0)).CastOrHttpException<Entity_Get_Response>();
		var currentMeta = ArticleMeta.Create(currentArticle.Entity.Properties[0].Value);

		currentMeta.Preface = preface;
		currentMeta.Author = author;
		currentMeta.PublishStartTime = publishStartTime;
		currentMeta.PublishStopTime = publishStopTime;

		List<EntityRelation> relations = new List<EntityRelation>();
		foreach(var categoryID in categories.Keys)
		{
			EntityRelation relation = null;
			bool featured = categories[categoryID];

			if(currentMeta.PublishedNow)
			{
				if(featured)
				{
					relation = new EntityRelation(EntityTypes.Category,
						categoryID + (byte)ArticleCategoryStatus.PublishedFeatured, true, false) 
						{ IndexValue1 = IndexBuilder.GetBytes(currentMeta.PublishStartTime) };
				}
				else
				{
					relation = new EntityRelation(EntityTypes.Category,
						categoryID + (byte)ArticleCategoryStatus.PublishedNotFeatured, true, false) 
						{ IndexValue1 = IndexBuilder.GetBytes(currentMeta.PublishStartTime) };
				}

				//add to main category index
				relations.Add(new EntityRelation(EntityTypes.Category, categoryID, true, false)
				{
					RelationType = CategoryRelationTypes.Article,
					IndexValue1 = IndexBuilder.GetBytes(currentMeta.PublishStartTime)
				});
			}
			else
			{
				if(featured)
				{
					relation = new EntityRelation(EntityTypes.Category,
						categoryID + (byte)ArticleCategoryStatus.NotPublishedFeatured, true, false) 
						{ IndexValue1 = IndexBuilder.GetBytes(currentMeta.CreateTime) };
				}
				else
				{
					relation = new EntityRelation(EntityTypes.Category,
						categoryID + (byte)ArticleCategoryStatus.NotPublishedNotFeatured, true, false) 
						{ IndexValue1 = IndexBuilder.GetBytes(currentMeta.CreateTime) };
				}
			}

			relations.Add(relation);
		}

		//добавляем в главную группу только если уже опубликовано
		if(currentMeta.PublishedNow)
		{
			relations.Add(new EntityRelation(EntityTypes.Root, EntityIDs.MainCategoryID, true, false) 
			{ IndexValue1 = IndexBuilder.GetBytes(currentMeta.PublishStartTime) });
		}

		//расписание публикаций
		var publishRelation = new EntityRelation(EntityTypes.Root, EntityIDs.PublishCategoryID, false, false);
		if(publishStartTime > DateTime.UtcNow && publishStartTime != DateTime.MaxValue)
			publishRelation.IndexValue1 = IndexBuilder.GetBytes(publishStartTime);

		if(publishStopTime > DateTime.UtcNow && publishStopTime != DateTime.MaxValue)
			publishRelation.IndexValue2 = IndexBuilder.GetBytes(publishStopTime);
		
		if(publishRelation.IndexValue1 != null || publishRelation.IndexValue2 != null)
			relations.Add(publishRelation);

		//construct messagesIn	
		List<MessageIn> messagesIn = new List<MessageIn>();
        messagesIn.Add(new Entity_Update(EntityTypes.Article, articleID, new Property(currentMeta.ToArray(), 0))
        {
            Locked = !currentMeta.PublishedNow,
        });
        messagesIn.Add(new Entity_UpdateRelations(EntityTypes.Article, articleID, true, relations.ToArray()));

		var messagesOut = OctoServices.Process(request, messagesIn.ToArray());
		CheckResponse(messagesOut);
	}

    //public static Article[] GetArticles(OOSRequest request, long[] ids)
    //{
    //    List<Article> articles = new List<Article>();

    //    List<MessageIn> entityMesIn = new List<MessageIn>();
    //    List<MessageIn> contletMesIn = new List<MessageIn>();

    //    foreach(var id in ids)
    //    {
    //        entityMesIn.Add(new Entity_Get(EntityTypes.Article, id)
    //            {
    //                ReturnParentIDs = true,
    //                ReturnAppContlets = true
    //            });
    //    }

    //    var entityResponses = OctoServices.Process(request, entityMesIn.ToArray());

    //    foreach(var entity in entityResponses)
    //    {
    //        List<long> contletIDs = new List<long>();
    //        var article = entity.CastOrHttpException<Entity_Get_Response>();
    //        foreach(var contlet in article.Entity.Contlets)
    //        {
    //            var contletMeta = PageMeta.Create(contlet.Meta.Value);
    //            if(contletMeta.Status == CompleteStatus.Completed)
    //                contletIDs.Add(contlet.ID);
    //        }

    //        contletMesIn.Add(new Contlet_GetBodies(EntityTypes.Article, article.Entity.ID, contletIDs.ToArray()));
    //    }

    //    var contletResponses = OctoServices.Process(request, contletMesIn.ToArray());

    //    var responses = OctoServices.Process(request, messagesIn.ToArray());
    //    int i = 0;
    //    while(i < responses.Length)
    //    {
    //        var allBodies = responses[i + 1].CastOrHttpException<Contlet_GetBodies_Response>();
    //        var article = responses[i].CastOrHttpException<Entity_Get_Response>();

    //        var articleMeta = ArticleMeta.Create(article.Entity.Properties[0].Value);

    //        //categories
    //        List<long> categories = new List<long>();
    //        foreach(var parentID in article.Entity.ParentIDs)
    //        {
    //            if(parentID != EntityIDs.MainCategoryID && parentID != EntityIDs.PublishCategoryID)
    //                categories.Add(parentID);
    //        }

    //        //pages and sections
    //        List<Page> pages = new List<Page>();
    //        foreach(var pageID in articleMeta.PagesOrder)
    //        {
    //            foreach(var currentPage in article.Entity.Contlets)
    //            {
    //                if(pageID == currentPage.ID)
    //                {
    //                    List<Section> sections = new List<Section>();
    //                    var pageMeta = PageMeta.Create(currentPage.Meta);

    //                    foreach(var sectionID in pageMeta.SectionsOrder)
    //                    {
    //                        foreach(var section in currentPage.Contlets)
    //                        {
    //                            if(section.ID == sectionID)
    //                            {
    //                                //var sectionMeta = SectionMeta.Create(section.Meta);
    //                                var sectionBody = allBodies.GetBody(section.ID);
    //                                sections.Add(new Section(section.ID, "sectino title",
    //                                    sectionBody.Text, sectionBody.Format));
    //                                break;
    //                            }
    //                        }
    //                    }

    //                    pages.Add(new Page(pageID, currentPage.Title, pageMeta.Status, sections.ToArray()));
    //                    break;
    //                }
    //            }
    //        }

    //        articles.Add(new Article(article.Entity.ID, articleMeta.Author, 
    //            article.Entity.Title, articleMeta.Preface,
    //            categories.ToArray(), pages.ToArray(),
    //            articleMeta.PublishStartTime, 
    //            articleMeta.PublishStopTime, articleMeta.CreateTime,
    //            articleMeta.UserID));
    //    }

    //    return articles.ToArray();
    //}

    public static Article GetArticle(OOSRequest request, long id)
    {
        var article = OctoServices.Process(request,
            new Entity_Get(EntityTypes.Article, id, 0) 
			{ 
				LoadAppContlets = ContletLoadFlags.ID, 
				ReturnParentIDsRelationType = 0
			}).CastOrHttpException<Entity_Get_Response>();

        //считываем категории статьи
        List<long> articleCategories = new List<long>();
        foreach(var categoryID in article.Entity.ParentIDs)
        {
            if(categoryID != EntityIDs.MainCategoryID && categoryID != EntityIDs.PublishCategoryID)
                articleCategories.Add(categoryID);
        }

        List<long> allPageIDs = new List<long>();
        foreach(var page in article.Entity.Contlets)
        {
            //исключаем дефолтный контлет
            if(page.ID != 1)
                allPageIDs.Add(page.ID);
        }

		List<Page> pages = new List<Page>();

		if(allPageIDs.Count > 0)
		{
			var allBodies = OctoServices.Process(request,
				new Contlet_GetBodies(EntityTypes.Article, id, allPageIDs.ToArray())
				).CastOrHttpException<Contlet_GetBodies_Response>();

			//pages and sections
			foreach(var pageID in allPageIDs)
			{
				var currentPage = article.Entity.GetContlet(pageID);

				List<Section> sections = new List<Section>();
				var pageMeta = PageMeta.Create(currentPage.Meta.Value);
				foreach(var section in currentPage.Contlets)
				{
					var sectionMeta = SectionMeta.Create(section.Meta.Value);

					var sectionBody = allBodies.GetBody(section.ID);
					sections.Add(new Section(section.ID, section.Title, sectionBody.Text,
						sectionBody.Format, sectionMeta.SortOrder));
				}

				pages.Add(new Page(pageID, currentPage.Title, pageMeta.Status, sections.OrderBy(f => f.SortOrder).ToArray(),
					pageMeta.SortOrder));
			}
		}

		var articleMeta = ArticleMeta.Create(article.Entity.Properties[0].Value);

        return new Article(id, articleMeta.Author, article.Entity.Title,
            articleMeta.Preface, articleCategories.ToArray(), pages.OrderBy(f=>f.SortOrder).ToArray(),
            articleMeta.PublishStartTime, articleMeta.PublishStopTime,
            articleMeta.CreateTime, articleMeta.UserID);
    }

	public static Section GetSection(OOSRequest request, long articleID, long sectionID)
	{
		var responses = OctoServices.Process(request,
			new Entity_Get(EntityTypes.Article, articleID) { LoadAppContlets = ContletLoadFlags.Meta },
			new Contlet_GetBodies(EntityTypes.Article, articleID, sectionID));

		var article = responses[0] as Entity_Get_Response;
		var bodies = responses[1] as Contlet_GetBodies_Response;
		var currentBody = bodies.GetBody(sectionID);

		var section = article.Entity.GetContlet(sectionID);
		if(section == null)
			throw new ApplicationException();

		var sectionMeta = SectionMeta.Create(section.Meta.Value);

		return new Section(sectionID, section.Title, currentBody.Text, currentBody.Format, sectionMeta.SortOrder);
	}

	public static void InsertSection(OOSRequest request, long articleID, long pageID,
		string name, string content, TextFormat format)
	{
		var article = OctoServices.Process(request,
			new Entity_Get(EntityTypes.Article, articleID) { LoadAppContlets = ContletLoadFlags.Meta }
			).CastOrHttpException<Entity_Get_Response>();

		var page = article.Entity.GetContlet(pageID);
		if(page == null)
			throw new ApplicationException();

        //вычисляем максимальный sortOrder для секций страницы
        short maxSectionSortOrder = 0;
        foreach(var section in page.Contlets)
        {
            var sectionMeta = SectionMeta.Create(section.Meta.Value);

            if(sectionMeta.SortOrder > maxSectionSortOrder)
                maxSectionSortOrder = sectionMeta.SortOrder;
        }

		var newSectionMeta = new SectionMeta((short)(maxSectionSortOrder + 1));

        var messagesOut = OctoServices.Process(request,
            new Contlet_Add(EntityTypes.Article, articleID, request.RequestID, pageID, name,
                new ContletBody(content, format), ContletFlags.Obsolete_AppRendering)
                {
                    Meta = new Meta(0, newSectionMeta.ToArray())
                });

		CheckResponse(messagesOut);
	}

	public static void UpdateSection(OOSRequest request, long articleID, long sectionID,
		string name, string content, TextFormat format)
	{
		var messageOut = OctoServices.Process(request,
			new Contlet_Update(EntityTypes.Article, articleID, sectionID)
        {
            Title = name,
            Body = new ContletBody(content, format)
        });

		CheckResponse(messageOut);
	}

	public static void DeleteSection(OOSRequest request, long articleID, long pageID, long sectionID)
	{
		var responses = OctoServices.Process(request, new Contlet_Delete(EntityTypes.Article, articleID, sectionID));
		CheckResponse(responses);
	}


	public static void InsertPage(OOSRequest request, long articleID, string name, CompleteStatus status)
	{
        List<MessageIn> messIn = new List<MessageIn>();

        var article = OctoServices.Process(request,
            new Entity_Get(EntityTypes.Category, articleID) { LoadAppContlets = ContletLoadFlags.Meta }
            ).CastOrHttpException<Entity_Get_Response>();

        short maxSortOrder = 0;
        foreach(var page in article.Entity.Contlets)
        {
            var pageMeta = PageMeta.Create(page.Meta.Value);
            if(pageMeta.SortOrder > maxSortOrder)
                maxSortOrder = pageMeta.SortOrder;
        }

        if(status != CompleteStatus.Completed)
        {
            //добавляем id страницы в pagesNotCompleted
            var articleMeta = ArticleMeta.Create(article.Entity.Properties[0].Value);
            articleMeta.AddNonCompletedPageID(request.RequestID);

            messIn.Add(new Entity_Update(EntityTypes.Article, articleID, new Property(articleMeta.ToArray(), 0)));
        }

#warning задать в настройках максимально возможное количество страниц
		var newPageMeta = new PageMeta(status, (short)(maxSortOrder + 1));
        messIn.Add(new Contlet_Add(EntityTypes.Article, articleID, request.RequestID, 0, name,
            new ContletBody("", TextFormat.Plain), ContletFlags.Obsolete_AppRendering)
            {
                Meta = new Meta(0, newPageMeta.ToArray())
            });

        var responses = OctoServices.Process(request, messIn.ToArray());
		CheckResponse(responses);
	}

	public static void DeletePage(OOSRequest request, long articleID, long pageID)
	{
        List<MessageIn> mesIn = new List<MessageIn>();

        mesIn.Add(new Contlet_Delete(EntityTypes.Article, articleID, pageID));

        var article = OctoServices.Process(request, new Entity_Get(EntityTypes.Article, articleID, 0)
                { LoadAppContlets = ContletLoadFlags.Meta }).CastOrHttpException<Entity_Get_Response>();

        var page = article.Entity.GetContlet(pageID);
        var pageMeta = PageMeta.Create(page.Meta.Value);

        //если страница незакончена то удаляем её из меты артикла
        if(pageMeta.Status != CompleteStatus.Completed)
        {
            var articleMeta = ArticleMeta.Create(article.Entity.Properties[0].Value);
            articleMeta.DeleteNonCompletedPageID(pageID);

            mesIn.Add(new Entity_Update(EntityTypes.Article, articleID, new Property(articleMeta.ToArray(), 0)));
        }

        var responses = OctoServices.Process(request, mesIn.ToArray());
		CheckResponse(responses);
	}

	public static CategoryArticlePreview[] GetRSSCategoryArticles(OOSRequest request, long categoryID, int offset,
		int count)
	{
		List<CategoryArticlePreview> previews = new List<CategoryArticlePreview>();

		var response = OctoServices.Process(request,
			//get articles for not featured category
			new Entity_GetChildren_Index(EntityTypes.Category, categoryID,
				(byte)CategoryIndexes.PublishTime, offset, count, true)
			{
				RelationType = CategoryRelationTypes.Article,
				ReturnPropertyIDs = new byte[] { 0 }
			}).CastOrHttpException<Entity_GetChildren_Response>();

		foreach(var preview in response.Entities)
		{
			var meta = ArticleMeta.Create(preview.Properties[0].Value);

			previews.Add(new CategoryArticlePreview(preview.ID, preview.Title, meta.Preface, meta.PublishStopTime,
				meta.PublishStartTime, meta.Author, false, meta.CreateTime, meta.UserID));
		}

		return previews.ToArray();
	}

	public static void GetCategoryArticles(OOSRequest request, long categoryID, int offset, 
        int count, out CategoryArticlePreview[] featuredArticles, out CategoryArticlePreview[] nonFeaturedArticles,
		out int totalCount)
	{
		List<CategoryArticlePreview> previews = new List<CategoryArticlePreview>();
		totalCount = 0;

		var responses = OctoServices.Process(request,
			//get articles for not featured category
			new Entity_GetChildPreviews_Index(EntityTypes.Category, categoryID + (short)ArticleCategoryStatus.PublishedNotFeatured,
				(byte)SubCategoryIndexes.PublishedNotFeatured_PublishTime, offset, count, true)
			{
		        ReturnTotalCountMax = 10000,
                ReturnPropertyIDs = new byte[] { 0 }
			},
			//get articles for featured category
            new Entity_GetChildPreviews_Index(EntityTypes.Category, categoryID + (short)ArticleCategoryStatus.PublishedFeatured,
				(byte)SubCategoryIndexes.PublishedFeatured_FeaturedTime, 0, _featuredArticlesCount, true)
			{
                ReturnPropertyIDs = new byte[] { 0 }
			});

		//featured
		var featuredResponse = responses[1].CastOrHttpException<Entity_GetChildPreviews_Response>();
		totalCount += featuredResponse.TotalCount;
		if(featuredResponse.Previews != null)
		{
			foreach(var preview in featuredResponse.Previews)
			{
				var meta = ArticleMeta.Create(preview.Properties[0].Value);

				previews.Add(new CategoryArticlePreview(preview.EntityID, preview.Title, meta.Preface, meta.PublishStopTime,
					meta.PublishStartTime, meta.Author, true, meta.CreateTime, meta.UserID));
			}
		}

		featuredArticles = previews.ToArray();
		previews.Clear();


		//not featured
		var regularResponse = responses[0].CastOrHttpException<Entity_GetChildPreviews_Response>();
		totalCount += regularResponse.TotalCount;

		foreach(var preview in regularResponse.Previews)
		{
			var meta = ArticleMeta.Create(preview.Properties[0].Value);

			previews.Add(new CategoryArticlePreview(preview.EntityID, preview.Title, meta.Preface, meta.PublishStopTime,
				meta.PublishStartTime, meta.Author, false, meta.CreateTime, meta.UserID));
		}

		nonFeaturedArticles = previews.ToArray();
	}

    public static MyPageArticlePreview[] GetMyArticles(OOSRequest request, MyArticleTypes type, int offset, 
		int count, out int totalCount, Category[] allCategories)
	{
		List<MyPageArticlePreview> articles = new List<MyPageArticlePreview>();
		totalCount = 0;

		var response = OctoServices.Process(request,
			new Entity_GetChildren_Index(EntityTypes.User, request.UserID, (byte)type, offset, count, true)
			{
				ReturnTotalCountMax = 10000,
				ReturnParentIDsRelationType = 0,
				ReturnPropertyIDs = new byte[] { 0 },
				//LoadAppContlets = ContletLoadFlags.Meta
			}).CastOrHttpException<Entity_GetChildren_Response>();


		totalCount = response.TotalCount;
		if(response.Entities != null)
		{
			foreach(var preview in response.Entities)
			{
				var articleMeta = ArticleMeta.Create(preview.Properties[0].Value);

				List<long> categoryIDs = new List<long>();
				foreach(var parentCategoryID in preview.ParentIDs)
				{
					foreach(var category in allCategories)
					{
						if(category.IsCategory(parentCategoryID))
						{
							categoryIDs.Add(parentCategoryID);
							break;
						}
					}
				}

				articles.Add(
					new MyPageArticlePreview(preview.ID, preview.Title, articleMeta.Preface, categoryIDs.ToArray(),
						articleMeta.CreateTime, articleMeta.PublishStartTime, articleMeta.PublishStopTime,
						preview.Contlets.Length, articleMeta.NonCompletePages, articleMeta.UserID, articleMeta.Author));
			}
		}

		return articles.ToArray();
	}


	public static void SavePagesOrder(OOSRequest request, long articleID,
		long[] newPageIDsOrder)
	{
        List<MessageIn> mesIn = new List<MessageIn>();

        var article = OctoServices.Process(request,
            new Entity_Get(EntityTypes.Article, articleID) { LoadAppContlets = ContletLoadFlags.Meta }
            ).CastOrHttpException<Entity_Get_Response>();

        for(short i = 0; i < newPageIDsOrder.Length; i++)
        {
            var currentPageID = newPageIDsOrder[i];

            foreach(var page in article.Entity.Contlets)
            {
                if(page.ID == currentPageID)
                {
                    var pageMeta = PageMeta.Create(page.Meta.Value);
                    pageMeta.SortOrder = (short)(i + 1);

                    mesIn.Add(new Contlet_Update(EntityTypes.Article, articleID, page.ID)
                    {
                        Meta = new Meta(0, pageMeta.ToArray())
                    });
                    break;
                }
            }
        }

        var response = OctoServices.Process(request, mesIn.ToArray());
		CheckResponse(response);
	}

	public static void SaveSectionsOrder(OOSRequest request, long articleID, long pageID, long[] newSectionsOrder)
	{
        List<MessageIn> mesIn = new List<MessageIn>();

		var article = OctoServices.Process(request,
            new Entity_Get(EntityTypes.Article, articleID) { LoadAppContlets = ContletLoadFlags.Meta }
			).CastOrHttpException<Entity_Get_Response>();

		var page = article.Entity.GetContlet(pageID);

        for(int i = 0; i < newSectionsOrder.Length; i++)
        {
            long currentSectionID = newSectionsOrder[i];

            foreach(var section in page.Contlets)
            {
                if(section.ID == currentSectionID)
                {
                    mesIn.Add(new Contlet_Update(EntityTypes.Article, articleID, section.ID)
                        {
                            Meta = new Meta(0, new SectionMeta((short)(i + 1)).ToArray())
                        });

                    break;
                }
            }
        }

        var response = OctoServices.Process(request, mesIn.ToArray());

		CheckResponse(response);
	}

	public static void UpdatePage(OOSRequest request, long articleID, long pageID,
		string title, CompleteStatus status)
	{
		var article = OctoServices.Process(request,
			new Entity_Get(EntityTypes.Article, articleID, 0) { LoadAppContlets = ContletLoadFlags.Meta }
			).CastOrHttpException<Entity_Get_Response>();
		var articleMeta = ArticleMeta.Create(article.Entity.Properties[0].Value);

		var page = article.Entity.GetContlet(pageID);
		var pageMeta = PageMeta.Create(page.Meta.Value);

        //сменился ли статус страницы
		if(pageMeta.Status == CompleteStatus.Completed)
		{
            if(status != CompleteStatus.Completed)
                //сменился статус страницы
                articleMeta.AddNonCompletedPageID(pageID);   
		}
		else
		{
            if(status == CompleteStatus.Completed)
                //сменился статус страницы
                articleMeta.DeleteNonCompletedPageID(pageID);
		}

	    pageMeta.Status = status;

        var responses = OctoServices.Process(request,
            new Entity_Update(EntityTypes.Article, articleID, new Property(articleMeta.ToArray(), 0)),
            new Contlet_Update(EntityTypes.Article, articleID, pageID)
        {
            Title = title,
            Meta = new Meta(0, pageMeta.ToArray())
        });

		CheckResponse(responses);
	}

	public static MainPageArticlePreview[] GetMainPreviews(OOSRequest request,
		int offset, int count, out int totalCount)
	{
		List<MainPageArticlePreview> result = new List<MainPageArticlePreview>();

		var response = OctoServices.Process(request, 
            new Entity_GetChildren_Index(EntityTypes.Root, EntityIDs.MainCategoryID, 
				(byte)CategoryIndexes.PublishTime, offset, count, true)
				{
					ReturnTotalCountMax = 100000,
                    ReturnPropertyIDs = new byte[] { 0 },
                    ReturnParentIDsRelationType = 0
				}).CastOrHttpException<Entity_GetChildren_Response>();

		totalCount = response.TotalCount;
		if(response.Entities != null)
		{
			foreach(var article in response.Entities)
			{
                List<long> categoryIDs = new List<long>();
                foreach(var parentID in article.ParentIDs)
                {
                    if(parentID != EntityIDs.MainCategoryID && parentID != EntityIDs.PublishCategoryID)
                        categoryIDs.Add(parentID);
                }

				var articleMeta = ArticleMeta.Create(article.Properties[0].Value);

				result.Add(new MainPageArticlePreview(article.ID, article.Title, articleMeta.Preface, 
                    articleMeta.PublishStartTime, articleMeta.PublishStopTime, articleMeta.Author, 
                    articleMeta.CreateTime, articleMeta.UserID, categoryIDs.ToArray()));
			}
		}

		return result.ToArray();
	}

	public static void SendMessagesSafe(OOSRequest request, MessageIn[] messages)
	{
		List<MessageIn> messagePacket = new List<MessageIn>();

		for(int i = 0; i < messages.Length; i++)
		{
			messagePacket.Add(messages[i]);
			if((i + 1) % _maxMessageCount == 0)
			{
				var response = OctoServices.Process(request, messages.ToArray());
				CheckResponse(response);
				messagePacket.Clear();
			}
		}

		if(messagePacket.Count > 0)
		{
			var response = OctoServices.Process(request, messages.ToArray());
			CheckResponse(response);
		}
	}
}